package com.eninus.notification.ios;

import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.cert.X509Certificate;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.concurrent.ConcurrentLinkedQueue;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;


public class PushManager extends Thread {
	private static final PushManager instance = new PushManager();
	 
    private PushManager() {}
 
    public static PushManager getInstance() {
        return instance;
    }
    private Thread selfThread;
    private boolean isInitialed;
    private boolean isStopFlag;
    private long feedbackLastRunTime;
    private KeyStore iKeyStore;
    private KeyManagerFactory iKeyManagerFactory;
    private TrustManagerFactory iTrustManagerFactory;
    private SSLContext sslContext;
	
	String pushServerName;
	int    pushPort;
	String feedbackSeverName;
	int	   feedbackPort;
	
	boolean isProductionEnvironment;
	String  jksPath;
	char[]  jksPassword;
	
	private ArrayList<PushClientThread> pushClientThreadArrayList;
	
	private FeedbackClientThread feedbackClientThread;
	
	
	
	ConcurrentLinkedQueue<IOSNotificationPacket> notificationCLinkedQueue;
	ConcurrentLinkedQueue<IOSErrorResponsePacket> errorResponseCLinkedQueue;
	ConcurrentLinkedQueue<IOSNotificationPacket> sendTimeCLinkedQueue;
	ConcurrentLinkedQueue<IOSFeedbackPacket> feedbackCLinkedQueue;
	
	private GsonBuilder gsonBuilder;
	private Gson gson;
	
	private void init(boolean pIsProductionEnvironment,String pJKSPath,String pJKSPassword) throws Exception {
		
		isProductionEnvironment=pIsProductionEnvironment;
		
		jksPath=pJKSPath;
		jksPassword=pJKSPassword.toCharArray();
		
		iKeyStore= KeyStore.getInstance("JKS");
		iKeyStore.load(new FileInputStream(jksPath),jksPassword);
		//
		Enumeration<String> aliasEnum = iKeyStore.aliases();
        while(aliasEnum.hasMoreElements()) {
            String alias = (String)aliasEnum.nextElement();
            System.out.println("****** Cert alias name: " + alias);
            X509Certificate certificate = (X509Certificate) iKeyStore.getCertificate(alias);
            System.out.println(certificate.getSubjectX500Principal().getName());

        }

		//
		iKeyManagerFactory = KeyManagerFactory.getInstance("SunX509");
		iKeyManagerFactory.init(iKeyStore, jksPassword);

		iTrustManagerFactory = TrustManagerFactory
				.getInstance(TrustManagerFactory.getDefaultAlgorithm());
		iTrustManagerFactory.init((KeyStore) null);
		
		//System.out.println("Default Trust Managers:");
		/*
		for (TrustManager trustManager : iTrustManagerFactory
				.getTrustManagers()) {
			if (trustManager instanceof X509TrustManager) {
				X509TrustManager x509TrustManager = (X509TrustManager) trustManager;
				for (X509Certificate acceptedIssuer : x509TrustManager
						.getAcceptedIssuers()) {
					System.out.println("\tAccepted issuer : "
							+ acceptedIssuer.getSubjectX500Principal()
									.getName());
				}
			}
		}*/
		sslContext = SSLContext.getInstance("TLS");
		sslContext.init(iKeyManagerFactory.getKeyManagers(),
				iTrustManagerFactory.getTrustManagers(), null);
       //
		pushClientThreadArrayList=new ArrayList<PushClientThread>();
		
		
		notificationCLinkedQueue=new ConcurrentLinkedQueue<IOSNotificationPacket>();
		errorResponseCLinkedQueue=new ConcurrentLinkedQueue<IOSErrorResponsePacket>();
		sendTimeCLinkedQueue= new ConcurrentLinkedQueue<IOSNotificationPacket>(); 
		feedbackCLinkedQueue=new ConcurrentLinkedQueue<IOSFeedbackPacket>();
		
		gsonBuilder = new GsonBuilder();
		gsonBuilder.registerTypeAdapter(IOSPayload.APS.class,
				new SerializerForIOSPayload());
		gson = gsonBuilder.create();
		//
		if(isProductionEnvironment){
			feedbackSeverName="feedback.push.apple.com";
		}else{
			feedbackSeverName="feedback.sandbox.push.apple.com";
		}
		feedbackPort=2196;
		
		feedbackClientThread=new FeedbackClientThread(iKeyManagerFactory, iTrustManagerFactory, feedbackSeverName, feedbackPort, feedbackCLinkedQueue);
		selfThread=new Thread(instance);
		
		isInitialed=true;
	}
	public IOSNotificationPacket pushNotification(int pIndentifier,int pExpiry,String pDeviceToken,String pAlertBody) throws Exception{
		//TODO deal with conflict with PK seq if preparator started
		IOSNotificationPacket iosNotificationPacket = new IOSNotificationPacket();
		
		iosNotificationPacket.identifier = pIndentifier;
		iosNotificationPacket.expiry = pExpiry;
		iosNotificationPacket.deviceToken = pDeviceToken;

		IOSPayload iosPayload = new IOSPayload();
		iosPayload.aps.alert.body = pAlertBody;
		iosNotificationPacket.payload = gson.toJson(iosPayload);
		iosNotificationPacket.pack();
		
		notificationCLinkedQueue.add(iosNotificationPacket);
		
		return iosNotificationPacket;	
	}
	public boolean sentConfirm(IOSNotificationPacket pIOSNotificationPacket){
		return sendTimeCLinkedQueue.contains(pIOSNotificationPacket);
	}
	public IOSNotificationPacket pushNotificationTheadSafe(int pIndentifier,int pExpiry,String pDeviceToken,String pAlertBody) throws Exception{
		//TODO deal with conflict with PK seq if preparator started
		GsonBuilder gsonBuilderLocal = new GsonBuilder();
		gsonBuilderLocal.registerTypeAdapter(IOSPayload.APS.class,
				new SerializerForIOSPayload());
		Gson gsonLocal = gsonBuilderLocal.create();
		
		IOSNotificationPacket iosNotificationPacket = new IOSNotificationPacket();
		
		iosNotificationPacket.identifier = pIndentifier;
		iosNotificationPacket.expiry = pExpiry;
		iosNotificationPacket.deviceToken = pDeviceToken;
		
		IOSPayload iosPayload = new IOSPayload();
		iosPayload.aps.alert.body = pAlertBody;
		iosNotificationPacket.payload = gsonLocal.toJson(iosPayload);
		iosNotificationPacket.pack();
		
		notificationCLinkedQueue.add(iosNotificationPacket);
		
		return iosNotificationPacket;	
	}
	public IOSNotificationPacket pushLegacyNotificationTheadSafe(String pDeviceToken,String pAlertBody) throws Exception{
		
		GsonBuilder gsonBuilderLocal = new GsonBuilder();
		gsonBuilderLocal.registerTypeAdapter(IOSPayload.APS.class,
				new SerializerForIOSPayload());
		Gson gsonLocal = gsonBuilderLocal.create();
		
		IOSNotificationPacket iosNotificationPacket = new IOSNotificationPacket();
		iosNotificationPacket.deviceToken = pDeviceToken;
		IOSPayload iosPayload = new IOSPayload();
		iosPayload.aps.alert.body = pAlertBody;
		iosNotificationPacket.payload = gsonLocal.toJson(iosPayload);
		iosNotificationPacket.pack();
		
		notificationCLinkedQueue.add(iosNotificationPacket);
		
		return iosNotificationPacket;	
	}
	
	public IOSNotificationPacket pushNotification(int pIndentifier,int pExpiry,String pDeviceToken,String pAlertBody,String pActionLocKey,String pLaunchImage,int pBadge,String pSound ) throws Exception{
		IOSNotificationPacket iosNotificationPacket = new IOSNotificationPacket();
		
		iosNotificationPacket.identifier = pIndentifier;
		iosNotificationPacket.expiry = pExpiry;
		iosNotificationPacket.deviceToken = pDeviceToken;
		
		IOSPayload iosPayload = new IOSPayload();
		iosPayload.aps.alert.body = pAlertBody;
		iosPayload.aps.alert.action_loc_key=pActionLocKey;
		iosPayload.aps.alert.launch_image=pLaunchImage;
		iosPayload.aps.badge=pBadge;
		iosPayload.aps.sound=pSound;
		iosNotificationPacket.payload = gson.toJson(iosPayload);
		iosNotificationPacket.pack();
		
		notificationCLinkedQueue.add(iosNotificationPacket);
		
		return iosNotificationPacket;	
	}
	public IOSNotificationPacket pushNotification(int pIndentifier,int pExpiry,String pDeviceToken,int pBadge) throws Exception{
		IOSNotificationPacket iosNotificationPacket = new IOSNotificationPacket();
		
		iosNotificationPacket.identifier = pIndentifier;
		iosNotificationPacket.expiry = pExpiry;
		iosNotificationPacket.deviceToken = pDeviceToken;
		
		IOSPayload iosPayload = new IOSPayload();
		iosPayload.aps.badge=pBadge;
		iosNotificationPacket.payload = gson.toJson(iosPayload);
		iosNotificationPacket.pack();
		
		notificationCLinkedQueue.add(iosNotificationPacket);
		
		return iosNotificationPacket;	
	}
	public IOSNotificationPacket pushNotification(int pIndentifier,int pExpiry,String pDeviceToken,String pActionLockey,String pLocKey,ArrayList<String> pLocArgs,String pLaunchImage,String pSound) throws Exception{
		IOSNotificationPacket iosNotificationPacket = new IOSNotificationPacket();
		
		iosNotificationPacket.identifier = pIndentifier;
		iosNotificationPacket.expiry = pExpiry;
		iosNotificationPacket.deviceToken = pDeviceToken;
		
		IOSPayload iosPayload = new IOSPayload();
		iosPayload.aps.alert.action_loc_key=pActionLockey;
		iosPayload.aps.alert.loc_key=pLocKey;
		iosPayload.aps.alert.loc_args=pLocArgs;
		iosPayload.aps.alert.launch_image=pLaunchImage;
		iosPayload.aps.sound=pSound;
		iosNotificationPacket.payload = gson.toJson(iosPayload);
		iosNotificationPacket.pack();
		
		notificationCLinkedQueue.add(iosNotificationPacket);
		
		return iosNotificationPacket;	
	}
	
	private void startPushClient(int pCount) throws Exception{
		
		if(isProductionEnvironment){
			pushServerName="gateway.push.apple.com";
		}else{
			pushServerName="gateway.sandbox.push.apple.com";
		}
		pushPort=2195;
		
		
		for(int i=0;i<pCount;i++){
		PushClientThread pushClientThread=new PushClientThread(i,iKeyManagerFactory,iTrustManagerFactory,pushServerName,pushPort,notificationCLinkedQueue,sendTimeCLinkedQueue,errorResponseCLinkedQueue);
		
		pushClientThread.start();
		pushClientThreadArrayList.add(pushClientThread);
		}
	}
	private void stopPushClient(int pCount) throws Exception{
		for(int i=0;i<pCount;i++){
			pushClientThreadArrayList.get(i).isCloseFlag=true;
			while(pushClientThreadArrayList.get(i).isAlive()){
				Thread.sleep(10);
			}
			pushClientThreadArrayList.remove(i);
		}
	}
	public int getAliveClientsCount(){
		for(int i=0;i<pushClientThreadArrayList.size();i++){
			if(!pushClientThreadArrayList.get(i).isAlive()){
				pushClientThreadArrayList.remove(i);
			}
		}
		return pushClientThreadArrayList.size();
	}
	private void stopAllPushClient() throws Exception{
		for(int i=0;i<pushClientThreadArrayList.size();i++){
			pushClientThreadArrayList.get(i).isCloseFlag=true;
			while(pushClientThreadArrayList.get(i).isAlive()){
				Thread.sleep(10);
			}
		}
		pushClientThreadArrayList.clear();
	}
	private void runFeedbackClient() throws Exception{
		if(feedbackClientThread.isAlive()) return;
		
		
		feedbackClientThread.start();
	}
	private void stopFeedbackClient() throws Exception{
		feedbackClientThread.isCloseFlag=true;
		while(feedbackClientThread.isAlive()){
			Thread.sleep(10);
		}
	}
	
	//TODO
	private void startPreparatorAndUpdater(String pConnecitonString){
		
	}
	//TODO
	private int  getAverageTransferRate(){
		
		return 0;
	}

public void startPushManger(int initialCount,String JKSFileName,String Password) throws Exception{
			if(isInitialed) return;
			
			instance.init(false, System.getProperty("user.dir")+"/"+JKSFileName, Password);
			instance.startPushClient(initialCount);
			selfThread.start();
					
}
//TODO
public void stopPushManager() throws Exception{
	   stopAllPushClient();
	   stopFeedbackClient();
	   isStopFlag=true;
	   while(selfThread.isAlive()){
			Thread.sleep(10);
		}
}
public void run() {
	if(!isInitialed) return;
		while(!isStopFlag){
			long currentMiilis=System.currentTimeMillis();
			try {
			if((currentMiilis-feedbackLastRunTime)/(1000*60*60*24)>0){
				runFeedbackClient();
				feedbackLastRunTime=System.currentTimeMillis();
			}
			//TODO gets sending rate,automatically increasing clients
		    Thread.sleep(1000*1);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		}
	}

}
